use crate::dynf::code::{Constant,CodeObject};
use crate::dynf::vm::{Scope,VM,Frame};
use std::sync::{Arc};
#[derive(Debug, PartialEq,Clone)]
pub struct DObject {
    typ:u32,
    value:DValue
}

pub type DObjectRef = Arc<DObject>;

impl DObject {
    pub fn new_int(n:i64) -> DObject {
        DObject { value: DValue::Int(n),typ:1 }
    }

    pub fn new_bool(b:bool) -> DObject {
        DObject { value: DValue::Bool(b),typ:2 }
    }

    pub fn new_float(n:f64) -> DObject {
        DObject {value:DValue::Float(n),typ:3 }
    }

    pub fn new_code(code:Box<CodeObject>) -> DObject {
        DObject {value:DValue::Code(Arc::new(code)),typ:4 }
    }

    pub fn new_string(string:String) -> DObject {
        DObject {value:DValue::String(string),typ:5 }
    }

    pub fn new_func(code:Arc<Box<CodeObject>>,scope:Scope) -> DObject {
        let func = FunctionObject {
            code,
            scope
        };
        DObject {value:DValue::Func(func),typ:6 }
    }

    pub fn null() -> DObject {
        DObject {value:DValue::NULL,typ:7 }
    }

    pub fn into_ref(self) -> DObjectRef {
        Arc::new(self)
    }

    pub fn is_float(&self) -> bool {
        match self.value {
            DValue::Float(_) => true,
            _ => false
        }
    }

    pub fn cast_int(&self) -> i64 {
        match self.value {
            DValue::Int(i) => i,
            ref val => panic!("{:?} not int number",val)
        }
    }

    pub fn cast_clone_code(&self) -> Arc<Box<CodeObject>> {
        match self.value {
            DValue::Code(ref i) => i.clone(),
            ref val => panic!("{:?} not  codeobject",val)
        }
    }

    pub fn cast_func(&self) -> &FunctionObject {
        match self.value {
            DValue::Func(ref f) => f,
            ref val => panic!("{:?} not  FunctionObject",val)
        }
    }

    pub fn cast_float(&self) -> f64 {
        match self.value {
            DValue::Float(n) => n,
            ref val => panic!("{:?} not float number",val)
        }
    }

    pub fn cast_bool(&self) -> bool {
        match  self.value {
            DValue::Bool(b) => b,
            ref val => panic!("{:?} not bool",val)
        }
    }

    pub fn into_float(&self) -> f64 {
        match self.value {
            DValue::Float(n) => n,
            DValue::Int(n) => n as f64,
            ref val => panic!("{:?} not number",val)
        }
    }

    pub fn is_number(&self) -> bool {
        match self.value {
            DValue::Float(n) => true,
            DValue::Int(n) => true,
            ref val => false
        }
    }

    pub fn typ(&self) -> u32 {
        self.typ
    }
}
#[derive(Debug, PartialEq,Clone)]
pub enum DValue {
    Int(i64),
    Bool(bool),
    Float(f64),
    Code(Arc<Box<CodeObject>>),
    String(String),
    Func(FunctionObject),
    NULL
}
#[derive(Debug, PartialEq,Clone)]
pub struct FunctionObject {
    code:Arc<Box<CodeObject>>,
    scope:Scope
}

impl FunctionObject {
    pub fn new(code:Arc<Box<CodeObject>>,scope:Scope) -> Self {
        FunctionObject {
            code,
            scope
        }
    }

    pub fn scope(&self) -> &Scope {
        &self.scope
    }

    fn fill_locals_from_args(&self,args:&Vec<DObjectRef>,scope:&Scope,code:&Arc<Box<CodeObject>>,vm:&VM) {
        let n = args.len();
        for i in 0..n {
            let arg_name = &code.arg_names[i];
            let arg = &args[i];
            scope.store_name(vm, arg_name, arg.clone());
        }
    }

    pub fn invoke(&self, func_args: Vec<DObjectRef>, vm: &VM) -> Option<DObjectRef> {
      let child_scope = self.scope.new_child_scope();
      self.fill_locals_from_args(&func_args,&child_scope, &self.code, vm);
     
      let  frame:Arc<Frame> = Arc::new(Frame::new(self.code.clone(),child_scope));
      vm.run_frame(frame)
     
    }
}

impl From<&Constant> for DObject {
    fn from(constant: &Constant) -> Self {
        match constant {
            Constant::Boolean {value} => DObject::new_bool(*value),
            Constant::Integer {value} => DObject::new_int(*value),
            Constant::Float {value} => DObject::new_float(*value),
            Constant::Code {code} => DObject::new_code(Box::new(code.clone()) ),
            Constant::String {value} => DObject::new_string(value.clone()),
            Constant::None => DObject::null(),
            _ => unimplemented!()
        }
    }
}